package com.adee.java;

import com.adee.util.Utils;

import java.util.concurrent.*;


public class Test022_0_ThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        /**
         * 注意！！！
         * 异步任务都要交给线程池处理！
         * 创建线程池要使用new ThreadPoolExecutor(...)方式，不要使用Executors.xxx方式，Executors.xxx默认workQueue
         * 长度为Integer.MAX_VALUE，会有OOM风险，而自己手动new线程池可以限定workQueue长度，并且自定义拒绝策略。
         *
         * 使用new的方式创建线程池：
         * int corePoolSize,  // 核心线程数
         * int maximumPoolSize,  // 最大线程数
         * long keepAliveTime,  // 超过核心线程数的线程空闲后经过多长时间销毁
         * TimeUnit unit,  // keepAliveTime时间单位
         * BlockingQueue<Runnable> workQueue,  // 阻塞队列，放任务，默认大小为Integer.MAX_VALUE
         * ThreadFactory threadFactory,  // 线程工厂
         * RejectedExecutionHandler handle  // 拒绝策略处理器
         *
         * 线程池控制状态变量AtomicInteger ctl的int值，低29位为workerCount，表示实际线程数，高3位为runState，表示线程池状态，
         * 如running、shutting down等等。
         * runState的取值：
         * RUNNING：可接收新任务，可处理队列中任务；
         * SHUTDOWN：不接收新任务，可处理队列中任务；
         * STOP：不接收新任务，不处理队列中任务，并且中断正在执行的任务；
         * TIDYING：所有任务已经完成，workerCount为0，并且变为TIDYING状态时回调terminated()方法；
         * TERMINATED：terminated()方法回调完成；
         * 这些状态值的顺序是有关系的，允许顺序比较。这些值随着时间单调递增，但不一定所有值都取到，可能会跳跃。
         * 状态值转变及触发时机：
         * RUNNING->SHUTDOWN：调用shutdown()方法；
         * (RUNNING or SHUTDOWN)->STOP：调用shutdownNow()方法；
         * SHUTDOWN->TIDYING：当workQueue为空，并且workerCount为0时；
         * STOP->TIDYING：workCount为0时；
         * TIDYING->TERMINATED：terminated()回调方法完成时，此时阻塞在awaitTermination()方法上的线程返回；
         *
         * 线程池工作流程：
         * 1. new ThreadPoolExecutor，线程并没有启动，只是创建tp对象，并设置如上参数；
         * 2. 执行execute方法添加任务，此时线程数workerCount还没达到corePoolSize，通过threadFactory创建线程放入池中，
         *    并t.start()启动线程；
         * 3. 继续添加任务，当此时workerCount==corePoolSize，将任务放到workQueue中，workQueue中的任务会被核心线程依次获取并执行；
         * 4. 任务产生速度比较快时，workQueue满了，此时再添加任务，会创建新的线程来执行该次任务；
         * 5. 继续添加任务，当此时实际线程数workerCount==maximumPoolSize，并且workQueue也满了，则执行拒绝策略，
         *    即最后一个参数RejectedExecutionHandler。
         *
         * 拒绝策略表示，当任务无法被线程池处理时（比如workQueue满了且以达到最大线程数、或者线程池状态为SHUTDOWN）：
         * 可以通过实现接口RejectedExecutionHandler来自定义拒绝策略，也可以使用jdk提供的拒绝策略。
         * jdk以ThreadPoolExecutor内部类的方式提供了4种拒绝策略：
         * AbortPolicy：丢弃，抛出异常，AbortPolicy 策略会直接抛出RejectedExecutionException 的 RuntimeException，程序可以采用重试或放弃；
         * DiscardPolicy：忽略，什么都不做，会直接默默丢失，不给你任何提示，不建议使用，容易莫名其妙丢任务；
         * DiscardOldestPolicy：剔除workQueue中最早的任务，并将当前任务入队；
         * CallerRunsPolicy：提交任务谁来执行这个任务，即将任务执行放在提交的线程里面，减缓了线程的提交速度，相当于负反馈。
         *
         * 面试题：
         * 一个线程池，core 7，max 20，queue 50，现有100个并发进来，如何分配？
         * 启动core 7个线程执行7个任务，入队50个任务，再启动13个线程，达到max 20个线程执行20个任务，此时已接收任务数
         * 为70，剩余30个（实际上小于等于30，因为线程数达到最大值时，已经执行完了若干任务）执行拒绝策略。
         */
        ThreadPoolExecutor tp2 = new ThreadPoolExecutor(
                7, 20,
                30, TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(50),
                Executors.defaultThreadFactory(),
                // new ThreadPoolExecutor.AbortPolicy());
                new MyRejectedExecutionHandler());
        for (int i = 0; i < 100; i++) {
            int finalI = i;
            tp2.execute(()->{
                Utils.sleep(1);
                System.out.println(finalI);
            });
        }

    }

    static class MyRejectedExecutionHandler implements RejectedExecutionHandler {

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            Utils.sleep(1);
            executor.execute(r); // 睡眠1秒后，再次扔到线程池中
        }
    }
}
